// Copyright (c) 2010 Manuel Peinado Gallego <manuel.peinado@gmail.com>
// Distributed under the MIT license

#include "stdafx.h"
#include "PythonManager.h"
#include "World.h"
#include <boost/circular_buffer.hpp>
#include <zthread/Task.h>
#include <zthread/Thread.h>
#include <zthread/Runnable.h>
#include <zthread/ThreadedExecutor.h>


// ~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-

void PythonStdIoRedirect::write( std::string const& str ) 
{
    if (m_outputs.capacity()<100)
        m_outputs.resize(100);
    m_outputs.push_back(str);
}

std::string PythonStdIoRedirect::getOutput()
{
    std::string ret;
    std::stringstream ss;
    boost::circular_buffer<std::string>::const_iterator it;
    for(it = m_outputs.begin(); it != m_outputs.end(); ++it)
    {
        ss << *it;
    }
    
    m_outputs.clear();
    ret =  ss.str();
    return ret;
}

PythonStdIoRedirect::ContainerType PythonStdIoRedirect::m_outputs;

// ~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-

PythonManager::PythonManager()
: m_initialized(false), m_running(false)
{
}

PythonManager::~PythonManager()
{
    // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    // We must NOT call Py_Finalize. See this: http://bit.ly/9mgB10 and
    // also this: http://bit.ly/bGuf4U 
    // Py_Finalize();
}

// ---------------------------------------------------------------------------



bpy::object& PythonManager::getContext()
{
    initialize();
    return m_mainNamespace;
}

// ---------------------------------------------------------------------------

std::string PythonManager::getStdOutput()
{
    initialize();
    return m_pythonStdioRedirector.getOutput();
}

// ---------------------------------------------------------------------------

class PythonScriptRunner : public ZThread::Runnable
{
    PythonManager& m_pyManager;
    std::string m_script;
    bool& m_flag;

public:

    PythonScriptRunner(PythonManager& pyManager, const std::string& script, 
                       bool& flag) 
    : m_pyManager(pyManager), m_script(script), m_flag(flag)
    {
    }

    void run() override
    {
        try
        {
            // Grab py context
            bpy::object& context = m_pyManager.getContext();
    
            // Execute script
            m_flag = true;
            bpy::exec(m_script.c_str(), context);
        }
        catch(bpy::error_already_set)
        {
            // Add the error message to the context's output redirections
            PyErr_Print();
        }

        // Print whatever is on the output redirector (including error
        // messages). But only if there's actually a message

        // TODO this is dangerous, the call to AfxMessageBox shouldn't be 
        // here

        std::string ansiMsg = m_pyManager.getStdOutput();
        if(!ansiMsg.empty())
        {
            CA2CT unicodeMsg(ansiMsg.c_str());
            AfxMessageBox(unicodeMsg);
        }

        // Would probably need a mutex for this but what are the chances?
        m_flag = false;
    }
};

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 

bool PythonManager::isRunning() const
{
    return m_running;
}

void PythonManager::runConcurrently(const std::string& script)
{
    if(m_running)
    {
        return;
    }

    // Create threading objects
    ZThread::Task task(new PythonScriptRunner(*this, script, m_running));
    ZThread::ThreadedExecutor exec;
    
    // And run
    exec.execute(task);
}

void PythonManager::stopCurrentScript()
{
    if(!m_running)
    {
        return;
    }
}

#include "pyInitAlg3d.h"
#include "pyInitColor.h"
#include "pyInitSelectable.h"
#include "pyInitDrawable.h"
#include "pyInitPickable.h"
#include "pyInitShape.h"
#include "pyInitNode.h"
#include "pyInitTransformNode.h"
#include "pyInitLightNode.h"
#include "pyInitShapeNode.h"
#include "pyInitNodeManager.h"
#include "pyInitWorld.h"
#include "pyInitSelectionManager.h"
#include "pyInitActionManager.h"
#include "pyInitActionShortcuts.h"
#include "pyInitUi.h"
#include "pyInitView3d.h"
#include "pyInitMainView.h"
#include "pyInitGlobals.h"

void initPython()
{
    // = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
    // Order is important

    pyInitAlg3d();
    
    pyInitColor();
    
    pyInitDrawable();
    pyInitShape();

    pyInitSelectable();
        pyInitPickable();
            pyInitNode();
                pyInitTransformNode();
                    pyInitLightNode();
                    pyInitShapeNode();
    
    pyInitNodeManager();

    pyInitWorld();

    pyInitSelectionManager();
    pyInitActionManager();
    pyInitActionShortcuts();
    pyInitUi();

    pyInitView3d();
        pyInitMainView();

    pyInitGlobals();
}


BOOST_PYTHON_MODULE(ed3d)
{
    // Setup the osg module info
    bpy::scope().attr("__doc__") = "Python bindings for the Ed3dmfc application\n";
    bpy::scope().attr("__name__") = "Ed3dmfc";
#ifdef _DEBUG
    bpy::scope().attr("__version__") = "debug";
#else
    scope().attr("__version__") = "release";
#endif
    bpy::scope().attr("__author__") = "Manuel Peinado (manuel.peinado@gmail.com) (C) 2010"; 
    bpy::scope().attr("__credits__") = 
        "Based on the following library :\n"
        "Boost Python Library V2 (http://www.boost.org)\n";
    initPython();
}


void PythonManager::initialize()
{
    if(m_initialized)
    {
        return;
    }
    else
    {
        m_initialized = true;
    }
    
    try
    {
        // initCore is a function created by the BOOST_PYTHON_MODULE macro which 
        // is used to initialize the Python module Core. At this point, your 
        // embedded python script may call import Core and then access any members 
        // of the module.
        PyImport_AppendInittab( "ed3d", &inited3d );

        // TODO use lazy initialization
        ::Py_Initialize();
        ASSERT(::Py_IsInitialized());
        
        // Import the _main_ module and create a dictionary 
        // object for the _main_ module's namespace
        m_mainModule = bpy::import("__main__");
        m_mainNamespace = m_mainModule.attr( "__dict__" );

        // Load the module, executing the initCore function
        m_coreModule = bpy::import( "ed3d" );

        // Add the module to the main namespace
        m_mainNamespace["ed3d"] = m_coreModule;

        // Replace stdio
        m_mainNamespace["PythonStdIoRedirect"] = 
            bpy::class_<PythonStdIoRedirect>(
                "PythonStdIoRedirect", 
                bpy::init<>()
            )
            .def(
                "write", 
                &PythonStdIoRedirect::write
            );

        bpy::object sys = bpy::import("sys");
        sys.attr("stderr") = m_pythonStdioRedirector;
        sys.attr("stdout") = m_pythonStdioRedirector;
    }
    catch(bpy::error_already_set)
    {
        ::PyErr_Print();
        CA2CT msg(getStdOutput().c_str());
        AfxMessageBox(msg);
    }
}